package chess;

import java.awt.Dimension;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import chess.piece.Piece;

/**
 * Classe Client (Cliente)
 * @author Beatriz Miho Anami
 * @author Lucas Rodrigues Anizelli
 * @author Marcos Okamura Rodrigues
 */
public class Client extends JFrame implements Constants{
	private ChessBoard chessBoard;
	private int actualTurn;
	private int actualPlayer;
	private Player player;
	private String gameResult;
	private JPanel panel;
	private JPanel chatPanel;
	private JTextArea chatInput;
	private JTextArea chatOutput;
	private JTable log;
	private JScrollPane logPane;
	private JScrollPane chatInputPane;
	private JScrollPane chatOutputPane;
	private JLabel[] leftLabel;
	private JLabel[] rightLabel;
	private JLabel[] upLabel;
	private JLabel[] downLabel;
	private Socket socket;
	private BufferedReader reader;
	private PrintWriter writer;
	private JDialog dialog;
	private JTextField name;
	private JTextField ip;
	private JTextField port;
	
	private static final Dimension MINIMUM_DIMENSION = new Dimension(720, 480);
	private static final Dimension DEFAULT_DIMENSION = new Dimension (800, 600);
	private static final long serialVersionUID = 1L;
	
	/**
	 * Função principal da aplicação.
	 * @param args	argumentos
	 */
	public static void main(String[] args) {
		new Client();
	}
	
	/**
	 * Construtor do cliente.
	 */
	public Client(){
		super();
		this.player = null;
		this.actualTurn = 0;
		this.chessBoard = new ChessBoard();
		this.gameResult = "None";
		this.actualPlayer = WHITE;
		this.showSetupDialog();
	}
	
	/**
	 * Apresenta a janela de configuração.
	 */
	private void showSetupDialog(){
		this.dialog = new JDialog(this);
		dialog.setResizable(false);
		dialog.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		dialog.setTitle("Setup");
		dialog.setLayout(null);
		dialog.setSize(250, 200);		
		dialog.setLocationRelativeTo(null);
		
		JLabel nameLabel = new JLabel("Name");
		nameLabel.setBounds(10, 20, 50, 20);
		JLabel ipLabel = new JLabel("IP (Server)");
		ipLabel.setBounds(10, 60, 80, 20);
		JLabel portLabel = new JLabel("Port");
		portLabel.setBounds(10, 100, 80, 20);
		
		this.name = new JTextField("Player");
		name.setBounds(100, 20, 120, 20);
		this.ip = new JTextField("127.0.0.1");
		ip.setBounds(100, 60, 120, 20);
		this.port = new JTextField("5000");
		port.setBounds(100, 100, 60, 20);
		
		dialog.addWindowListener(new ClientWindowListener());
		dialog.add(nameLabel);
		dialog.add(portLabel);
		dialog.add(ipLabel);
		dialog.add(name);
		dialog.add(ip);
		dialog.add(port);
		dialog.setVisible(true);
	}
	
	/**
	 * Cria um cliente.
	 */
	private void createClient() {
		int portNumber = Integer.parseInt(port.getText());
		try {
			this.socket = new Socket(ip.getText(), portNumber);
			this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			this.writer = new PrintWriter(socket.getOutputStream());
		} catch(Exception ex) {
			JOptionPane.showMessageDialog(this, "Connection could not be established");
			System.exit(1);
		}
		ClientThread clientThread = new ClientThread(socket);
		clientThread.start();
	}
	
	/**
	 * Inicializa a GUI.
	 */
	private void initGUI(){
		String playerColor;
		switch(player.getColor()){
			case WHITE: playerColor = " (White)"; break;
			case BLACK: playerColor = " (Black)"; break;
			default: playerColor = " (Spectator)"; break;
		}
		this.setTitle("Chess - Client" + playerColor);
		//this.setLocationRelativeTo(null);
		this.setMinimumSize(MINIMUM_DIMENSION);
		this.setSize(DEFAULT_DIMENSION);
		this.panel = new JPanel();
		panel.setLayout(null);
		chessBoard.setBounds(20, 20, 512, 512);
		panel.add(this.chessBoard);
		
		// criação dos labels
		this.leftLabel = new JLabel[NUMBER_OF_ROWS];
		this.rightLabel = new JLabel[NUMBER_OF_ROWS];
		this.upLabel = new JLabel[NUMBER_OF_COLUMNS];
		this.downLabel = new JLabel[NUMBER_OF_COLUMNS];
		
		if(player.getColor() == WHITE || player.getColor() == SPECTATOR){
			for(int row = 0; row < NUMBER_OF_ROWS; row++){
				this.leftLabel[row] = new JLabel(String.valueOf(row + 1));
				this.rightLabel[row] = new JLabel(String.valueOf(row + 1));
			}
			for(int column = 0; column < NUMBER_OF_COLUMNS; column++){
				this.upLabel[column] = new JLabel(String.valueOf((char) (column + 'a')));
				this.downLabel[column] = new JLabel(String.valueOf((char) (column + 'a')));
			}
		} else {
			for(int row = 0; row < NUMBER_OF_ROWS; row++){
				this.leftLabel[row] = new JLabel(String.valueOf(NUMBER_OF_ROWS - row));
				this.rightLabel[row] = new JLabel(String.valueOf(NUMBER_OF_ROWS - row));
			}
			for(int column = 0; column < NUMBER_OF_COLUMNS; column++){
				this.upLabel[column] = new JLabel(String.valueOf((char) (NUMBER_OF_COLUMNS - (column + 1) + 'a')));
				this.downLabel[column] = new JLabel(String.valueOf((char) (NUMBER_OF_COLUMNS - (column + 1) + 'a')));
			}
		}
		
		for(int row = 0; row < NUMBER_OF_ROWS; row++){
			panel.add(this.leftLabel[row]);
			panel.add(this.rightLabel[row]);
		}
		for(int column = 0; column < NUMBER_OF_COLUMNS; column++){
			panel.add(this.upLabel[column]);
			panel.add(this.downLabel[column]);
		}
		this.drawLabels();
		
		// criação do log
		String[][] logData = new String[100][3];
		String[] logTitle  = {"", "White", "Black"};
		this.log = new JTable(logData, logTitle);
		log.setEnabled(false);
		log.setCellSelectionEnabled(false);
		for(int i = 0; i < 100; i++){
			log.setValueAt(String.valueOf(i + 1), i, 0);
		}
		this.logPane = new JScrollPane(this.log);
		log.getColumnModel().getColumn(0).setMaxWidth(30);
		logPane.setBorder(BorderFactory.createTitledBorder("Log"));
		logPane.setBounds(600, 0, 200, 300);
		
		// criação do chat
		this.chatOutput = new JTextArea(); 
		chatOutput.setEditable(false); 
		chatOutput.setLineWrap(true);
		this.chatOutputPane = new JScrollPane(this.chatOutput);
		chatOutputPane.setPreferredSize(new Dimension(150, 300));
		this.chatInput = new JTextArea();
		this.chatInput.setLineWrap(true);
		this.chatInputPane = new JScrollPane(this.chatInput);
		chatInputPane.setPreferredSize(new Dimension(150, 100));
		this.chatPanel = new JPanel();
		chatPanel.setLayout(new BoxLayout(chatPanel, BoxLayout.Y_AXIS));
		chatPanel.setBorder(BorderFactory.createTitledBorder("Chat"));
		chatPanel.setBounds(600, 300, 200, 300);
		chatPanel.add(this.chatOutputPane);
		chatPanel.add(this.chatInputPane);
		
		panel.add(logPane);
		panel.add(this.chatPanel);
		this.add(this.panel);
		this.setDefaultCloseOperation(EXIT_ON_CLOSE);
		chessBoard.addMouseListener(new ClientMouseListener());
		this.addComponentListener(new ClientComponentListener());
		chatInput.addKeyListener(new ClientKeyListener());
		this.setVisible(true);
	}
	
	/**
	 * Desenha os labels do tabuleiro.
	 */
	private void drawLabels(){
		int x = chessBoard.getX();
		int y = chessBoard.getY();
		int size = chessBoard.getHeight();
		int squareSize = size / NUMBER_OF_ROWS;
		for(int row = 0; row < NUMBER_OF_ROWS; row++){
			leftLabel[row].setBounds(x - 12, y + (NUMBER_OF_ROWS - row) * squareSize - squareSize / 2, 10, 10);
			rightLabel[row].setBounds(x + size + 2, y + (NUMBER_OF_ROWS - row) * squareSize - squareSize / 2, 10, 10);
		}
		
		for(int column = 0; column < NUMBER_OF_COLUMNS; column++){
			upLabel[column].setBounds(x + column * squareSize + squareSize / 2, y - 18, 10, 16);
			downLabel[column].setBounds(x + column * squareSize + squareSize / 2, y + size + 2, 10, 16);
		}
	}
	
	/**
	 * Classe ClientWindowistener
	 * @author Beatriz Miho Anami
	 * @author Lucas Rodrigues Anizelli
	 * @author Marcos Okamura Rodrigues
	 */
	private class ClientWindowListener extends WindowAdapter{
		/**
		 * Verifica se a janela foi fechada.
		 * @param windowEvent	evento da janela
		 */
		@Override
		public void windowClosed(WindowEvent windowEvent){
			createClient();
		}
	}

	/**
	 * Classe ChessMouseListener
	 * @author Beatriz Miho Anami
	 * @author Lucas Rodrigues Anizelli
	 * @author Marcos Okamura Rodrigues
	 */
	private class ClientMouseListener extends MouseAdapter{
		private Position piecePosition;
		
		/**
		 * Verifica se o mouse foi clicado.
		 * @param mouseEvent	evento do mouse
		 */
		@Override
		public void mouseClicked(MouseEvent mouseEvent) {
			if(player != null && gameResult.equals("None")){
				if(player.getColor() == actualPlayer){
					int squareSize = chessBoard.getHeight() / NUMBER_OF_ROWS;
					int row, column;
					if(actualPlayer == WHITE){
						row = 7 - (mouseEvent.getY() / squareSize);
						column = mouseEvent.getX() / squareSize;
					} else {
						row = mouseEvent.getY() / squareSize;
						column = 7 - (mouseEvent.getX() / squareSize);
					}
					
					if(row >= 0 && column >= 0 && row < NUMBER_OF_ROWS && column < NUMBER_OF_COLUMNS){
						if(piecePosition == null){
							Position newPosition = new Position(row, column);
							if(chessBoard.trySelectPiece(actualPlayer, newPosition)){
								piecePosition = newPosition;
							}
						} else {
							String playString = chessBoard.tryMove(actualPlayer, piecePosition, new Position(row, column), NO_LETTER);
							if(!playString.isEmpty()){
								// verificação de fim de jogo e construção do log
								if(chessBoard.isCheck(1 - actualPlayer)){
									if(chessBoard.isCheckMate(1 - actualPlayer)){
										playString += "#";
										gameResult = "Checkmate";
									} else {
										playString += "+";
									}
								} else if(chessBoard.isStaleMate(1 - actualPlayer)){
									gameResult = "Stalemate";
								}
								//envia os dados do log para o servidor
								writer.print("GAME;" + actualTurn + ";" + actualPlayer + ";"
									 + playString + ";" + gameResult + "\n");
								writer.flush();
								piecePosition = null;
								actualPlayer = 1 - actualPlayer;
							} else {
								if(chessBoard.trySelectPiece(actualPlayer, new Position(row, column))){
									piecePosition = new Position(row, column);
								} else {
									piecePosition = null;
								}
							}
						}
					}
				}
			}
		}
	}
	
	/**
	 * Classe ChessComponentListener
	 * @author Beatriz Miho Anami
	 * @author Lucas Rodrigues Anizelli
	 * @author Marcos Okamura Rodrigues
	 */
	private class ClientComponentListener extends ComponentAdapter {
		/**
		 * Verifica se o componente foi redimensionado.
		 * @param componentEvent	evento do componente
		 */
		
		@Override
		public void componentResized(ComponentEvent componentEvent){
			int width = panel.getWidth();
			int height = panel.getHeight();
			int boardSize, leftWidth, rightWidth, boardX, boardY;
			if(height < (width * 3 / 4)){
				leftWidth = height;
				boardSize = (int) (leftWidth * 0.9);
				boardY = (int) (leftWidth * 0.05);
			} else {
				leftWidth = width * 3 / 4;
				boardSize = (int) (leftWidth * 0.9);
				boardY = (height - boardSize) / 2;
			}
			rightWidth = width - leftWidth;	
			boardX = (int) (leftWidth * 0.05);
			chessBoard.setBounds(boardX, boardY, boardSize, boardSize);
			logPane.setBounds(leftWidth + 1, 0, rightWidth, height / 2);
			chatPanel.setBounds(leftWidth + 1, height / 2 + 1, rightWidth, height / 2);
			drawLabels();
			panel.updateUI();
			panel.repaint();
		}
	}
	
	/**
	 * Classe ChessKeyListener
	 * @author Beatriz Miho Anami
	 * @author Lucas Rodrigues Anizelli
	 * @author Marcos Okamura Rodrigues
	 */
	private class ClientKeyListener extends KeyAdapter {
		/**
		 * Verifica se a tecla foi acionada.
		 * @param keyEvent	evento da tecla
		 */
		@Override
		public void keyTyped(KeyEvent keyEvent){
			if(keyEvent.getKeyChar() == '\n'){
				String playerColor;
				switch(player.getColor()){
					case WHITE: playerColor = " (White) "; break;
					case BLACK: playerColor = " (Black) "; break;
					default: playerColor = " (Spectator) "; break;
				}
				if(player != null){
					writer.print("CHAT;" + player.getName() + playerColor + ": " + chatInput.getText());
					writer.flush();
				}
				chatInput.setText("");
			}
		}
	}
	
	/**
	 * Interpreta a messagem.
	 * @param message	mensagem a ser interpretada
	 */
	public void parseMessage(String message){
		String[] messageSplit = message.split(";");
		actualTurn = Integer.parseInt(messageSplit[1]);
		actualPlayer = Integer.parseInt(messageSplit[2]);
		String playString = messageSplit[3];
		gameResult = messageSplit[4];
		log.setValueAt(playString, actualTurn, actualPlayer + 1);
		
		int firstColumn, firstRow, lastColumn, lastRow;
		char pieceLetter = NO_LETTER;
		//roque
		if(playString.charAt(0) == 'O'){
			if(actualPlayer == WHITE){
				firstRow = Position.FIRST_WHITE_ROW;
			} else {
				firstRow = Position.FIRST_BLACK_ROW;
			}
			lastRow = firstRow;
			firstColumn = Piece.KING;
			if(playString.length() < 5){
				lastColumn = Piece.KNIGHT_2;
			} else {
				lastColumn = Piece.BISHOP_1;
			}
		} else {
			int index = 0;
			if(Character.isUpperCase(playString.charAt(0))){
				index++;
			} else {
				if(playString.length() > 5 && playString.charAt(5) == '='){
					pieceLetter = playString.charAt(6);
				}
			}
			firstColumn = playString.charAt(index) - 'a';
			firstRow = playString.charAt(index + 1) - '1';
			lastColumn = playString.charAt(index + 3) - 'a';
			lastRow = playString.charAt(index + 4) - '1';
		}	
		
		if(player.getColor() != actualPlayer){
			chessBoard.tryMove(actualPlayer, new Position(firstRow, firstColumn),
				new Position(lastRow, lastColumn), pieceLetter);
		}		
		
		//JOptionPane com a mensagem ao ganhador
		if(gameResult.equals("Checkmate")){
			if(actualPlayer == WHITE){
				JOptionPane.showMessageDialog(panel, "Whites wins!!!", gameResult, JOptionPane.DEFAULT_OPTION);
			} else {
				JOptionPane.showMessageDialog(panel, "Blacks wins!!!", gameResult, JOptionPane.DEFAULT_OPTION);
			}
		} else if(gameResult.equals("StaleMate")){
			JOptionPane.showMessageDialog(panel, "Draw!!!", gameResult, JOptionPane.DEFAULT_OPTION);
		} else {
			if(actualPlayer == BLACK){
				actualTurn++;
			}
			actualPlayer = 1 - actualPlayer;
		}
	}
	
	/**
	 * Classe ClientThread
	 * @author Beatriz Miho Anami
	 * @author Lucas Rodrigues Anizelli
	 * @author Marcos Okamura Rodrigues
	 */
	private class ClientThread extends Thread {
		private Socket socket;
		
		/**
		 * Construtor da ClientThread.
		 * @param socket	socket de transmissão
		 */
		public ClientThread(Socket socket){
			this.socket = socket;
		}
		/**
		 * Inicia a thread.
		 */
		@Override
		public void run() {
			String input;
			try {
				while((input = reader.readLine()) != null) {
					System.out.println(input);
					String action = input.substring(0, 4);
					if(action.equals("CONF")){
						String[] inputSplit = input.split(";");
						int playerView = Integer.parseInt(inputSplit[1]);
						player = new Player(playerView, name.getText()); 
						actualPlayer = WHITE;
						chessBoard.addPlayer(WHITE);
						chessBoard.addPlayer(BLACK);
						chessBoard.setPlayerView(playerView);
						initGUI();
					} else if(action.equals("GAME")) {
						parseMessage(input);
					} else {
						chatOutput.append(input.substring(5) + '\n');
					}
				}
				socket.close();
				JOptionPane.showMessageDialog(panel, "Server has closed");
			}catch(Exception exception) {
				exception.printStackTrace();
			}
		}
	}
}
